/*
  (C) 2007 - Boston University
  Contact: Michael Ocean 

	This class is taken directly from a CS511 submission

  This work is a part of the larger snBench project.  Information
  can be found at: http://csr.bu.edu/snbench 

  This program is free software; you can redistribute it and/or
  modify it under the terms of Version 2 of the GNU General Public 
  License.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free
  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package stepide.opcodes;

import java.util.Hashtable;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Set;
import java.io.File;

import stepide.xml.XmlDocument;
import stepide.xml.XmlNode;

import org.w3c.dom.NodeList;

/**
 * Overview: OpcodeLibrary is a container class where all opcodes (in Opcodes) can be
 * stored. Items are stored as a <Key,Value> pair, with the fully qualified name
 * of an opcode serving as the key, and the Opcode object serving as the key.
 * 
 */

public class OpcodeLibrary {
	private static OpcodeLibrary _library = null;
	private Hashtable<String, Opcode> opcodes;
	public static String DefaultOpcodeFilePath = "stepide/opcodes/opcodes.xml";

	private OpcodeLibrary() {
		opcodes = BuildOpcodeTable();
	}

	/**
	 * Effects: Builds the opcode function table by parsing the default opcode file if it
	 * exists, or asks the user to provide one.
	 * 
	 * @return a Collection view of the values (Opcodes) contained in this
	 *         OpcodeLibrary
	 */
	private Hashtable<String, Opcode> BuildOpcodeTable() {
		XmlDocument document = new XmlDocument();
		document.load(new File(DefaultOpcodeFilePath));
		// select all "opcode" nodes
		NodeList opcodeNodes = document.selectNodes("//opcode"); 
		Hashtable<String, Opcode> opcodeTable = new Hashtable<String, Opcode>(50);

		for (int i = 0; i < opcodeNodes.getLength(); i++) {
			XmlNode xmlNode = new XmlNode(opcodeNodes.item(i));
			String fqn = opcodeNodes.item(i).getAttributes().getNamedItem("fqn").getNodeValue();
			String opcodeName = opcodeNodes.item(i).getAttributes().getNamedItem("name").getNodeValue();
			String snafu = opcodeNodes.item(i).getAttributes().getNamedItem("snafu").getNodeValue();
			boolean overloaded = Boolean.parseBoolean(opcodeNodes.item(i).getAttributes().getNamedItem("overloaded").getNodeValue());
			String description = xmlNode.selectNode("description").getTextContent();

			NodeList typeSigNodes = xmlNode.selectNodes("typesig");
			TypeSignature[] types = new TypeSignature[typeSigNodes.getLength()];

			for (int j = 0; j < typeSigNodes.getLength(); j++) {
				XmlNode typeSigXmlNode = new XmlNode(typeSigNodes.item(j));
				int id = Integer.parseInt(typeSigNodes.item(j).getAttributes().getNamedItem("id").getNodeValue());
				boolean allowInfinite = false;
				if (typeSigNodes.item(j).getAttributes().getNamedItem("allowInfinite") != null) {
					allowInfinite = Boolean.parseBoolean(typeSigNodes.item(j).getAttributes().getNamedItem("allowInfinite").getNodeValue());
				}
				String outputType = typeSigXmlNode.selectNode("output").getAttributes().getNamedItem("type").getNodeValue();

				Hashtable inputTypes = new Hashtable<Integer, String>();
				NodeList inputNodes = typeSigXmlNode.selectNodes("input");
				for (int k = 0; k < inputNodes.getLength(); k++) {
					String type = inputNodes.item(k).getAttributes().getNamedItem("type").getNodeValue();
					int arg = Integer.parseInt(inputNodes.item(k).getAttributes().getNamedItem("arg").getNodeValue());
					inputTypes.put(arg, type);
				}

				types[j] = new TypeSignature(id, allowInfinite, outputType, inputTypes);
			}

			Opcode opcode = new Opcode(opcodeName, fqn, snafu, description, overloaded, types);
			opcodeTable.put(opcode.getFqn(), opcode);
		}

		return opcodeTable;
	}

	public static OpcodeLibrary getLibrary(String path) {
		if (_library != null) {
			_library.opcodes = null;
			_library = null;
		}
		
		DefaultOpcodeFilePath = path;
		_library = new OpcodeLibrary();
		
		return _library;
	}
	
	public static OpcodeLibrary getLibrary() {
		if (_library == null) {
			_library = new OpcodeLibrary();
		}

		return _library;
	}

	/**
	 * Inserts an Opcode into the library
	 */
	public void put(Opcode opcode) {
		opcodes.put(opcode.getFqn(), opcode);
	}

	/**
	 * @return Opcode
	 */
	public Opcode get(String fqn) 
	{
		return opcodes.get(fqn);
	}

	/**
	 * @return a Collection view of the values (Opcodes) contained in this
	 *         OpcodeLibrary
	 */
	public Collection<Opcode> values() {
		return opcodes.values();
	}

	

	/**
	 * @return an enumeration of the Opcodes. Use the Enumeration methods on the
	 *         returned object to fetch the elements sequentially.
	 */
	public Enumeration<Opcode> elements() {
		return opcodes.elements();
	}

	/**
	 * Tests if the specified object is a key in this OpcodeLibrary.
	 * 
	 * @param opcode
	 *            possible key
	 * @return true if and only if the specified object is a key in this
	 *         OpcodeLibrary; false otherwise.
	 */
	public boolean containsKey(Opcode opcode) {
		return opcodes.containsKey(opcode.getFqn());
	}

	/**
	 * Returns true if this OpcodeLibrary maps one or more keys to this value.
	 * 
	 * @param o
	 *            value whos presence in this OpcodeLibrary is to be tested
	 * @return true if this map maps one or more keys to the specified value.
	 */
	public boolean containsValue(Opcode opcode) {
		return opcodes.containsValue(opcode);
	}

	/**
	 * Returns an enumeration of the keys (fully qualified names) in this
	 * OpcodeLibrary
	 * 
	 * @return an enumeration of the keys in this OpcodeLibrary.
	 */
	public Enumeration<String> getOpcodeNameList() {
		return opcodes.keys();
	}

	/**
	 * Tests if this OpcodeLibrary maps no keys to values.
	 * 
	 * @return true if this OpcodeLibrary maps no keys to values; false
	 *         otherwise.
	 */
	public boolean isEmpty() {
		return opcodes.isEmpty();
	}

	/**
	 * @return a set view of the keys fully qualified names) contained in this
	 *         OpcodeLibrary.
	 */
	public Set<String> keySet() {
		return opcodes.keySet();
	}

	/**
	 * Removes the key (and its corresponding value) from this OpcodeLibrary.
	 * This method does nothing if the key is not in the OpcodeLibrary.
	 * 
	 * @return the value to which the key had been mapped in this OpcodeLibrary,
	 *         or null if the key did not have a mapping.
	 */
	public Opcode remove(Object key) {
		return opcodes.remove(key);
	}

	/**
	 * @return the number of keys in this OpcodeLibrary.
	 */
	public int size() {
		return opcodes.size();
	}
}